#!/usr/local/bin/python3
# *_* coding: UTF-8 *_*
# @IDE: PyCharm
# @Version: Python3.97
# @Author: Kendrick.Kc
# @Email: 509556985@qq.com
# @File: threading.py
# @Inst: 线程入口
# @Time: 2022/8/28 14:46
# -----


import django

django.setup()  # 为在子进程中调用django应用，需在子进程启动时初始化django
import os
import signal
import platform
import threading
import time
import traceback

from basic.models import ProcessNumber

from binance_dgs.get_volatility import get_db_volatility
from binance_dgs.extract import ExtractSymbol
from binance_dgs.strategy import DGS


class TradingAppStart:
    def __init__(self, dgs: list, m_lock, binance):
        """
        多线程
        :param dgs: DGS数据列表
        """
        super(TradingAppStart, self).__init__()
        self.dgs = dgs
        self.m_lock = m_lock  # 进程锁
        self.binance = binance  # 中间件类
        self.t_lock = threading.Lock()
        self.one_run = False
        self.threads = []
        self.run(self.dgs)

    def run(self, inst_id):
        """
        遍历创建线程
        :return:
        """
        while True:
            if not self.one_run:
                self.one_run = True
                for i in range(self.binance.parameter.cpu_count_number):
                    try:
                        self.start_th(inst_id[i]['info']['inst_id'], inst_id[i])
                    except IndexError:
                        pass

            self.is_balance()  # 这里重新获取高波动率币种
            self.alive_th()

    def is_balance(self):
        """ 判断是否继续开下一个币种 """

        self.t_lock.acquire()
        self.m_lock.acquire()
        if not self.binance.cache_get(self.binance.parameter.db_coll_program) \
                and len(self.threads) < self.binance.parameter.cpu_count_number:

            res_vol = get_db_volatility(self.binance)
            for i in range(len(res_vol)):
                _ts = self.binance.cache_get(self.binance.parameter.db_coll_trading_symbol)

                if len(_ts) >= self.binance.parameter.trading_count_number:
                    break

                # 判断该交易对是否已在交易状态
                if res_vol[i]['symbol'] in _ts:
                    continue

                # 计算当前币种形成DGS
                try:
                    res_dgs = ExtractSymbol(self.binance, res_vol[i]['symbol'])
                    res_dgs = res_dgs.extract()
                    if res_dgs:
                        self.binance.logger_info(f"开启新的交易对: {res_vol[i]['symbol']}")
                        self.start_th(res_vol[i]['symbol'], res_dgs)
                        # 更改手动停止的状态
                        self.binance.cache_set(self.binance.parameter.db_coll_program, False)
                        break
                except:
                    self.binance.logger_info(f"启动新交易对报错：{traceback.format_exc()}")
                    break
        self.m_lock.release()
        self.t_lock.release()

    def start_th(self, instId, dgs):
        """
        创建线程
        :param instId: 币种
        :param dgs: DGS数据
        :return:
        """
        # 线程
        th = threading.Thread(target=DGS, args=(instId, dgs, self.m_lock, self.t_lock, self.binance))
        th.setDaemon(True)
        th.setName(instId)
        th.start()
        self.threads.append(th)

    def alive_th(self):
        """
        监控线程是否存活
        :return:
        """
        while True:
            try:

                time.sleep(5)

                program = self.binance.cache_get(self.binance.parameter.db_coll_program)

                # 当此子进程没有交易对交易时，删除db进程数据
                if self.process_stop(program) == "-1":
                    self.binance.logger_info(f"子进程{os.getpid()}交易结束.")
                    os.kill(os.getpid(), signal.SIGINT if platform.system() == "Windows" else signal.SIGKILL)
                    break

                # 当前线程结束后，不够钱开新交易对，这里监控账户余额，钱够则开，相反则等待并监控
                if not program:
                    if len(self.threads) < self.binance.parameter.cpu_count_number:
                        balance = self.binance.cache_get(self.binance.parameter.db_coll_balance)
                        if balance[self.binance.parameter.db_coll_balance] > self.binance.parameter.this_margin:
                            self.is_balance()

                for t in range(len(self.threads)):
                    if self.threads[t].is_alive():
                        continue
                    self.threads.pop(t)
                    break

            except Exception as e:
                self.binance.logger_info(f"子进程: {os.getpid()} 循环错误: {str(e)}")
                continue

    def process_stop(self, program):
        """
        当此子进程没有交易对交易时，删除db进程数据(为小程序端提供的监控数据)
        :param program:
        :return: 程序状态 ('-1', '立即停止'), ('1', '运行中'), ('0', '平仓停止')
        """
        self.t_lock.acquire()
        self.m_lock.acquire()

        status = None
        if len(self.threads) == 0:
            process_obj = ProcessNumber.objects.filter(exchange=self.binance.exchange_name,
                                                       strategy=self.binance.strategy_name).first()
            if len(process_obj.process) == 0 and program:
                process_obj.status = "-1"
            else:
                process_obj.process = process_obj.process.remove(os.getpid())
            status = process_obj.status
            process_obj.save()

        self.m_lock.release()
        self.t_lock.release()
        return status
